package vip.liux.backend.infrastructure.utils;

import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.jwt.*;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import vip.liux.contracts.security.UserDetailsInfo;

import java.security.interfaces.RSAPublicKey;
import java.time.Instant;
import java.util.List;

import static vip.liux.backend.infrastructure.utils.JwtConverter.ClaimNames.*;
import static vip.liux.backend.infrastructure.utils.KeyGenerator.getPrivateKey;
import static vip.liux.backend.infrastructure.utils.KeyGenerator.getPublicKey;

public class JwtConverter {

    private static final Logger log = LoggerFactory.getLogger(JwtConverter.class);

    public static final String ISSUER = "issuer";

    public static final String AUDIENCE = "audience";

    public static final long EXPIRY = 36000L;

    public final static JwtDecoder jwtDecoder = jwtDecoder();

    public final static JwtEncoder jwtEncoder = jwtEncoder();

    public interface ClaimNames {
        String USER_NAME = "name";
        String NAME = "givenname";
        String SUR_NAME = "surname";
        String USER_ID = "nameidentifier";
        String ROLE = "role";
        String EMAIL = "emailaddress";
        String EMAIL_VERIFIED = "email_verified";
        String PHONE_NUMBER = "phone_number";
        String PHONE_NUMBER_VERIFIED = "phone_number_verified";
    }

    public static TokenModel generateToken(UserDetailsInfo userDetail) {
        Instant now = Instant.now();
        // @formatter:off
        JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder()
                .issuer(ISSUER) // iss 签发者
                .subject(userDetail.getUsername()) // sub 主题
                .audience(List.of(AUDIENCE)) // aud 接收者
                .expiresAt(now.plusSeconds(EXPIRY)) // exp 过期时间
                .notBefore(now) // nbf 生效时间
                .issuedAt(now)
                .id(userDetail.getId()); // jti JWT ID，唯一标识符

        if (userDetail.getAuthorities() != null && !userDetail.getAuthorities().isEmpty()) {
            claimsBuilder.claim(ROLE, AuthorityUtils.authorityListToSet(userDetail.getAuthorities()));
        }
        if (userDetail.getUsername() != null && !userDetail.getUsername().isEmpty()) {
            claimsBuilder.claim(USER_NAME, userDetail.getUsername());
        }
        if (userDetail.getName() != null && !userDetail.getName().isEmpty()) {
            claimsBuilder.claim(NAME, userDetail.getName());
        }
        if (userDetail.getSurName() != null && !userDetail.getSurName().isEmpty()) {
            claimsBuilder.claim(SUR_NAME, userDetail.getSurName());
        }
        if (userDetail.getId() != null && !userDetail.getId().isEmpty()) {
            claimsBuilder.claim(USER_ID, userDetail.getId());
        }
        if (userDetail.getEmail() != null && !userDetail.getEmail().isEmpty()) {
            claimsBuilder.claim(EMAIL, userDetail.getEmail());
        }
        if (userDetail.isEmailConfirmed()) {
            claimsBuilder.claim(EMAIL_VERIFIED, true);
        }
        if (userDetail.getPhoneNumber() != null && !userDetail.getPhoneNumber().isEmpty()) {
            claimsBuilder.claim(PHONE_NUMBER, userDetail.getPhoneNumber());
        }
        if (userDetail.isPhoneNumberConfirmed()) {
            claimsBuilder.claim(PHONE_NUMBER_VERIFIED, true);
        }

        JwtClaimsSet claims = claimsBuilder.build();

        // @formatter:on
        Jwt encode = jwtEncoder.encode(JwtEncoderParameters.from(claims));
        // Serialize to compact form
        return new TokenModel(encode.getTokenValue(), EXPIRY);
    }

    public static JwtClaimsSet verifyToken(String token) {
        Jwt jwt;
        try {
            jwt = jwtDecoder.decode(token);
        } catch (BadJwtException failed) {
            log.debug("Failed to authenticate since the JWT was invalid");
            throw new InvalidBearerTokenException(failed.getMessage(), failed);
        } catch (JwtException failed) {
            throw new AuthenticationServiceException(failed.getMessage(), failed);
        }
        return JwtClaimsSet.builder()
                .claims(claims -> claims.putAll(jwt.getClaims()))
                .build();
    }

    static JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withPublicKey((RSAPublicKey) getPublicKey()).build();
    }

    static JwtEncoder jwtEncoder() {
        JWK jwk = new RSAKey.Builder((RSAPublicKey) getPublicKey()).privateKey(getPrivateKey()).build();
        JWKSource<SecurityContext> jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk));
        return new NimbusJwtEncoder(jwkSource);
    }

    public record TokenModel(String accessToken, long expiresIn) {
    }
}
